/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#ifndef __SCENE_OBJECT_H__
#define __SCENE_OBJECT_H__

#include "MeshData.h"
#include "Object.h"

namespace PQEngine
{
	class TerrainGroupObject;
	class CameraObject;
	class SkyManager;

	enum SceneRenderQueueGroupID
	{
		SCENE_RENDER_QUEUE_STAR_FIELD       = Ogre::RENDER_QUEUE_SKIES_EARLY + 0,
		SCENE_RENDER_QUEUE_MOON_BACKGROUND = Ogre::RENDER_QUEUE_SKIES_EARLY + 1,
        SCENE_RENDER_QUEUE_SKY_DOME         = Ogre::RENDER_QUEUE_SKIES_EARLY + 2,
		SCENE_RENDER_QUEUE_MOON            = Ogre::RENDER_QUEUE_SKIES_EARLY + 3,
        SCENE_RENDER_QUEUE_SUN             = Ogre::RENDER_QUEUE_SKIES_EARLY + 4,
        SCENE_RENDER_QUEUE_CLOUDS          = Ogre::RENDER_QUEUE_SKIES_EARLY + 5,
        SCENE_RENDER_QUEUE_GROUND_FOG      = Ogre::RENDER_QUEUE_SKIES_EARLY + 6,
	};

	class SceneResourceGroupNames
	{
	public:
		static const Ogre::String RESOURCE_GROUP_NAME_ENVIRONMENT;
		static const Ogre::String RESOURCE_GROUP_NAME_TERRAIN;
	};

	enum SceneResourceLocation
	{
		LOC_FILE_SYSTEM,//local file system
		LOC_MPQ_ARCHIVE,
		LOC_GPACK_ARCHIVE,//our homemade archive system
	};
	enum SceneResourceType
	{
		SCENE_RESOURCE_UNKNOWN,
		SCENE_RESOURCE_MESH,
		SCENE_RESOURCE_SCRIPT_CSHARP,
		SCENE_RESOURCE_ZIP,
		SCENE_RESOURCE_FILE_BLEND,
		SCENE_RESOURCE_M2_MODLE,//blizzard m2 model
	};

	/*Singleton class that manage all scene resource group.
	*/
	class SceneResourceGroup
	{
	public:
		static const std::string FILE_SYSTEM;//type file folder
		static const std::string ZIP;//zip archive
		enum ResourceGroupType
		{
			RES_GROUP_FILE_SYSTEM=0,
			RES_GROUP_ZIP=1,
			//add new type here
			RES_GROUP_COUNT

		};
	protected:
		SceneResourceGroup();
	public:
		static SceneResourceGroup& getSingleton();

		inline std::vector<std::string>& getPathes(ResourceGroupType type)
		{
			return _lists[type];
		}

		/*Generate unique group name
		*/
		std::string genName();
		ResourceGroupType getType(std::string path);
		static std::string getGroupLocType(ResourceGroupType type);

		bool add(std::string filePath);
		bool exist(std::string path);
		bool legal(std::string path);

	private:
		int _count;
		std::vector<std::string> _lists[RES_GROUP_COUNT];//pathe lists
	};

	/*None ogre native resource resource format load interface.
		@remark
		None Ogre native resource load implementation should inherit this class.
	*/
	class SceneResourceLoadImpl
	{
	public:
		SceneResourceLoadImpl(SceneObject* sceneObject, std::string path,SceneResourceLocation loc=LOC_FILE_SYSTEM)
			: _sceneObject(sceneObject),_path(path),_loc(loc)
		{
		}
		virtual GameObject* load()=0;//must implement this
	protected:
		SceneResourceLocation _loc;
		SceneObject* _sceneObject;
		std::string _path;
	};
	/*Load resource in file system into specified scene object.
	*/
	class SceneResourceLoader
	{
	public:
		SceneResourceLoader(SceneObject* sceneObject, std::string path,SceneResourceLocation loc=LOC_FILE_SYSTEM);
		~SceneResourceLoader();
		
		/*Load resource in to engine object
		*/
		GameObject* load();

		GameObject* loadFromLocalFS();
		GameObject* loadFromMPQ();
		GameObject* loadFromGPK();

		inline static SceneResourceType getResourceType(std::string type)
		{
			if(type=="mesh"||type=="MESH"){
				return SCENE_RESOURCE_MESH;
			}
			if(type=="cs"||type=="CS"){
				return SCENE_RESOURCE_SCRIPT_CSHARP;
			}
			if(type=="blend"||type=="BLEND"){
				return SCENE_RESOURCE_FILE_BLEND;
			}
			if(type=="m2"||type=="M2"){
				return SCENE_RESOURCE_M2_MODLE;
			}
			return SCENE_RESOURCE_UNKNOWN;
		}
		
		inline std::string getName()
		{
			int pos=_path.find_last_of("/");
			std::string name=_path.substr(pos+1);
			return name;
		}

		inline std::string getExt()
		{
			int pos=_path.find_last_of(".");
			std::string ext=_path.substr(pos+1);
			return ext;
		}

		inline std::string getParentFolder()
		{
			int pos=_path.find_last_of("/");
			std::string folder=_path.substr(0, pos+1);//parent folder
			return folder;
		}

		inline SceneResourceType getResourceType()
		{
			return _type;
		}

	private:
		GameObject* loadMesh();//load native mesh file into ogre
		GameObject* loadBlend();//load blend file into ogre
		GameObject* loadM2();//load m2 model into ogre

		GameObject* loadMeshFromGPK();//
	private:
		SceneResourceLocation _loc;//
		std::string _path;//whole path
		SceneObject* _sceneObject;
		SceneResourceType _type;
		SceneResourceLoadImpl* _impl;
	};

	/** Used to get and set  Scene render property
        @remarks
			
    */
	class SceneRenderProperty
	{
	private:
		Ogre::PolygonMode _mode;//0 points 1 wireframe 2 solid
		Ogre::ColourValue _backgroundColor;
		Ogre::ColourValue _ambient;
		Ogre::ColourValue _fogColor;
		
		float _fogDensity;
		SceneObject* _sceneObject;
	public:
		SceneRenderProperty(SceneObject* obj);

		inline Ogre::ColourValue& getBackground()
		{
			return _backgroundColor;
		}

		inline Ogre::ColourValue& ambient()
		{
			return _ambient;
		}

		inline Ogre::ColourValue& getFogColor()
		{
			return _fogColor;
		}

		inline float getFogDensity()
		{
			return _fogDensity;
		}

		void setBackground(Ogre::ColourValue color);
		void setAmbient(Ogre::ColourValue color);
		//void setFogColor(Ogre::ColourValue color);
		

		void setPolyMode(Ogre::PolygonMode mode);
	};

	
	/** A scene SceneObject is an independent scene container.
        @remarks
			This is used to manage a whole scene
    */
	class SceneObject
		: Object
	{
	public:
		//use friend to implement property change
		friend void SceneRenderProperty::setBackground(Ogre::ColourValue color);
		friend void SceneRenderProperty::setAmbient(Ogre::ColourValue color);
		friend void SceneRenderProperty::setPolyMode(Ogre::PolygonMode type);
		typedef std::map<Ogre::String,GameObject*> GameObjectMap;//used search objects by name
	protected:
		SceneObject(Ogre::String hWnd,int width,int height);


		inline void setBackground(Ogre::ColourValue color)
		{
			if(_viewport){
				_viewport->setBackgroundColour(color);
			}
		}

		inline void setAmbient(Ogre::ColourValue color)
		{
			if(_sceneManager){
				_sceneManager->setAmbientLight(color);
			}
		}

		void setPolyMode(Ogre::PolygonMode mode);

	public:
		~SceneObject();
		static SceneObject* create(Ogre::String hWnd,int width,int height);

		void enableSkyDome(bool enabled);

		inline Ogre::RenderWindow* getRenderWindow()
		{
			return _window;
		}

		/*Create entity object use mesh resource name.
		@param meshName Mesh resource name.
		*/
		EntityObject *createEntityObject(Ogre::String meshName);
		LightObject *createLightObject(int lightType);

        /** Creates a new named SceneNode as a child of camera node.
        */
		Ogre::SceneNode* createCameraFollowSceneNode(const Ogre::String& name, const Ogre::Vector3& translate = Ogre::Vector3::ZERO, const Ogre::Quaternion& rotate = Ogre::Quaternion::IDENTITY);

		/** Creates a new unnamed SceneNode as a child of camera node.
        */
		Ogre::SceneNode* createCameraFollowSceneNode(const Ogre::Vector3& translate = Ogre::Vector3::ZERO, const Ogre::Quaternion& rotate = Ogre::Quaternion::IDENTITY );




		GameObject* getRootGameObject();

		/*Select game object by mouse on viewport screen in current scene.
		@param x The mouse position x in the screen.
		@param y The mouse position y in the screen.
		*/
		GameObject* selectObject(int x,int y);
		
		/*GET(not select) game object by mouse on viewport screen in current scene.
		@param x The mouse position x in the screen.
		@param y The mouse position y in the screen.
		*/
		GameObject* getObject(int x,int y);

		/*GET(not select) game object by mouse ray on viewport screen in current scene.
		@param ray The mouse ray shot from mouse pointer.
		*/
		GameObject* getObject(Ogre::Ray ray);

		/*Select game object by pointer in current scene.
		@param object The game object's pointer.
		*/
		GameObject* selectObject(GameObject* object);

		/*Select game object by id in current scene.
		@param id The game object's object id.
		*/
		GameObject* selectObject(int id);
		
		
		/*Get mouse ray by screen coord
		*/
		Ogre::Ray getRay(int x,int y);

		/*Get center screen mouse ray 
		*/
		Ogre::Ray getRay();

		inline Ogre::SceneNode* createChildSceneNode()
		{
			Ogre::SceneNode* node=_sceneManager->getRootSceneNode()->createChildSceneNode();
			return node;
		}

		inline Ogre::SceneNode* getRootSceneNode()
		{
			Ogre::SceneNode* node=_sceneManager->getRootSceneNode();
			return node;
		}

		inline static bool isActivate(SceneObject* sceneObject)
		{
			if(!sceneObject){
				return false;
			}
			return sceneObject->isActivate();
		}

		inline static void setActiveScene(SceneObject* sceneObject)
		{
			if(sceneObject==NULL){
				return;
			}

			if(_activeScene){
				_activeScene->setIsActivate(false);
			}
			sceneObject->setIsActivate(true);
			
		}

		/* Static method to get actived scene object
			@remark
			At a specific time only ONE SceneObject is Actived.
		*/
		inline static SceneObject* getActive()
		{
			assert(_activeScene);
			return _activeScene;
		}
		

		inline bool hasObject(Ogre::String name)
		{
			GameObjectMap::iterator it= _gameObjects.find(name);
			if(it!=_gameObjects.end()){
				return true;
			}
			return false;
		}

		/* Update scene in seconds
		*/
		void update(float deltaT);
		GameObject* addResource(std::string path);
		/*Add GPK resource and return the object created by using this resource
		*/
		GameObject* addGPKResource(std::string path);
		
		void addCharacterModel(std::string path);//add resource in wow archive(mpq archive)
		void add2DSprite(Sprite2D* sprite);

		/*Create a flat terrain chunk in position slotX slotY with diffuse texture index in terrain texture GPK archive.
		@param slotX The X coord of slot position.
		@param slotX The Y coord of slot position.
		@param diffuseTexIndex The diffuse texture index in terrain texture GPK archive.
		*/
		TerrainObject* createFlatTerrain(int slotX,int slotY,int diffuseTexIndex);
		void createSphereObject(Ogre::Vector3 position);

		//get current selected game object
		GameObject* getGameObject();

		void startEditTerrain();
		void stopEditTerrain();

		void addTerrainLayer(std::string textureName);
		void selectTerrainLayer(int layer);
		void setTerrainEditMode(TerrainEditMode mode);

		TerrainEditMode getTerrainEditMode();

		void selectTerrainBrush(int index);
		int getNumTerrainTexture();
		int getNumTerrainBrushTexture();

		Ogre::String getTerrainTextureName(int index);
		Ogre::String getTerrainBrushTextureName(int index);
		
		GPKFile* getTerrainTextureFile(int index);
		GPKFile* getTerrainTextureFile(std::string name);

		GPKFile* getTerrainBrushTextureFile(int index);
		GPKFile* getTerrainBrushTextureFile(std::string name);
		
		/*Get the terrain group object.
		@Note Each scene can only have one such group.
		*/
		TerrainGroupObject* getTerrainGroup();

		TerrainObject* selectTerrain(int index);
		/*Add texture file from local file system according to file path.
		*/
		bool addTerrainTextureFromLocalFS(std::string filePath);
		bool addTerrainBrushTextureFromLocalFS(std::string filePath);

		/*Manual ogre objects (only makes sense for manual loading)
		*/
		Ogre::Mesh* createMesh(const Ogre::String &name, Ogre::ManualResourceLoader *loader);
		Ogre::Skeleton* createSkeleton(const Ogre::String &name, Ogre::ManualResourceLoader *loader);

		/*Must change static active SceneObject
		*/
		inline void setIsActivate(bool b)
		{
			_isActivate= b;
			if(_isActivate){
				_activeScene=this;
			}
		}

		inline bool isActivate()
		{
			return _isActivate;
		}

		inline CameraObject* getCamera()
		{
			return _camera;
		}

		void resetScreenResolution(int width,int height);

		inline Ogre::SceneManager* getSceneManager()
		{
			return _sceneManager;
		}

		inline SceneRenderProperty& getRenderProperty()
		{
			return *_renderProperty;//right to do so?
		}
		
		/** Gets the number of triangles rendered in the last update() call. */
		inline int getTriangleCount()
		{
			if(_window){
				return _window->getTriangleCount();
			}
			return 0;
		}

	private:
		void initDefaultScene();
		
	private:
		static SceneObject* _activeScene;
		bool _isActivate;
		GameObject* _rootObject;//hold root gameobject
		GameObject* _selectedObject;//current selected gameobject to manipulate
		CameraObject* _camera;

		Ogre::RaySceneQuery* _raySceneQuery;
		MeshData* _selectMeshData;//used load selected mesh and test selection

		//GameObjectManager* _gameObjectManger;
		SpriteManager* _spriteManager;
		TerrainManager* _terrainManager;
		SkyManager* _skyManager;
		//EnvironmentManager* _environmentManager;
		GameObjectMap _gameObjects;

		Ogre::RenderWindow* _window;
		
		Ogre::Viewport* _viewport;
		Ogre::SceneManager* _sceneManager;

		SceneRenderProperty* _renderProperty;
		
	public:
		//Please inject input into scene.
		void OnMouseMove(int x,int y);
	private:
		int _mouseX;
		int _mouseY;
	};

}
#endif
